#include <loader.h>
#include <tss.h>
#include <string.h>
#include <i386.h>
#include <mm.h>
#include <mm_copy.h>
#include <sched.h>
#include <vga.h>
#include <debug.h>
#include <device.h>


#define CANT_PILAS_ANILLO_3 5

#define GET_DS(pl) (((pl)?4:2) << 3)
#define GET_CS(pl) (((pl)?3:1) << 3)

task task_table[MAX_PID + 1];

/* pid "actual" */
pid current_pid = MAX_PID;

extern tss our_tss;
extern void task_isr_ret();

int remove_last = 0;
 
//esta funcion inicializa las estructuras necesarias para el loader
void loader_init(void) {

	//seteo la frecuencia del PIT
	init_timer(1);

	//inicializo la unica TSS que voy a utilizar
	memset(&our_tss, 0, sizeof(our_tss));
	//la pila esta en el final de la memoria
	our_tss.esp0 = PILA_0 + 0x1000;
	//seteo segmento de la pila para anillo cero
	our_tss.ss0 = 0x10;

	//seteo el tr en el selector 5 de la gdt
	ltr(0x5 << 3);

	//inicializo la tabla de tareas
	memset(task_table, 0, MAX_PID * sizeof(task));
}
 
void init_timer(uint_32 frequency)
{
	uint_32 divisor = 1193180 / frequency;
	outb(0x43, 0x36);
	uint_8 l = (uint_8)(divisor & 0xFF);
	uint_8 h = (uint_8)( (divisor>>8) & 0xFF );

	outb(0x40, l);
	outb(0x40, h);
}

uint_32 
loader_setup_stack(void* py_page, int mode, int pl, void * _main)
{
	//debug_log(" STACK %x \n", py_page);
	
	mm_page * p_dir = (mm_page *) rcr3(); 
	mm_page * table_entry = NULL;
	
	table_entry = mm_get_vt_addr_entry(p_dir, (void *) PAGE_COPY_BUFFER);
	table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S;
	table_entry->base = ((uint_32) py_page) >> 12;
	
	static uint_32 *stack;
	stack = (uint_32*) (PAGE_COPY_BUFFER + 0xFFC);
	//if(mode) stack--;
	
	//uint_32 eip ;
	//uint_32 esp ;
	//uint_32 ebp ;
	
	if (mode)
	{
		static uint_32 *stack_prev;
		stack_prev = (uint_32*) (PAGE_COPY_BUFFER + 0xFFC);
		stack_prev--;
		//eip = *((uint_32*) 0xff7fffe8);
		//esp = *((uint_32*) 0xff7ffff4);
		//ebp = *((uint_32*) (PILA_0 - 4* 11));
	}
	
	//seteo una pila para que la tarea arranque
	*(stack - 19) = 0x0; //EBX, aparentemente el gcc también lo pushea
	*(stack - 18) = 0x0; //EBP 
	*(stack - 17) = (uint_32) task_isr_ret; //EIP 
	*(stack - 16) = 0x0; //EAX (paramentro de switchto)
	*(stack - 5) = 0x0; //EAX
	
	if(mode == 0)
	{
		*(stack - 15) = pl | GET_DS(pl); //GS
		*(stack - 14) = pl | GET_DS(pl); //FS
		*(stack - 13) = pl | GET_DS(pl); //ES
		*(stack - 12) = pl | GET_DS(pl); //DS
		*(stack - 11) = 0x0; //EBP
		*(stack - 10) = 0x0; //EDI
		*(stack - 9) = 0x0; //ESI
		*(stack - 8) = 0x0; //EDX
		*(stack - 7) = 0x0; //ECX
		*(stack - 6) = 0x0; //EBX
		*(stack - 4) = (uint_32) _main; //EIP
		*(stack - 3) = pl | GET_CS(pl); //CS
		*(stack - 2) = 0x202; //EFLAGS (numero magico segun furfaro)
		*(stack - 1) = PILA_3 + 0x1000; //ESP 
		*(stack) = pl | GET_DS(pl); //SS
	}
/*
	if (mode)
	{
		*(stack - 11) = ebp; //EBP
		*(stack - 4) = eip; //EIP
		*(stack - 1) = esp; //ESP 
	}
*/
	

	//*stack es el RIP
	//debug_log("EIP Luke %x \n", *(stack - 4));
	//debug_log("CS Luke %x \n", *(stack - 3));
	//debug_log("EFL Luke %x \n", *(stack - 2));
	//debug_log("ESP Luke %x \n", *(stack - 1));
	//debug_log("SS Luke %x \n", *(stack - 0));
	//debug_log("EBP Luke %x \n", *(stack - 11));
	//breakpoint();
		
		
	//elimino la entrada temporal
	*((uint_32*) table_entry) = 0;
	
	stack = (uint_32*) (PILA_0 + 0xFFC);
	//if(mode) stack--;
	uint_32 esp_ret = (uint_32) (stack - 19);
	
	return esp_ret;
}

pid
get_new_pid()
{
	pid new_pid;
	//Busco una entrada libre para la pcb
	for (new_pid = 0; new_pid < MAX_PID && task_table[new_pid].status != TASK_FREE; new_pid++);
	return new_pid;
}	

//funcion para hacer un fork
pid loader_fork(void) { 
	//breakpoint();
	mm_page *table_entry, *h_cr3; 
	
	h_cr3 = mm_copy_current_directory_cow();

	pid new_pid; 
	new_pid = get_new_pid();

	//inicializo la pila de anillo 0 del hijo para que vuelva del switch_to 
	void * pila_0;
	table_entry = mm_get_vt_addr_entry((void *) h_cr3, (void *) PILA_0);
	//obtengo la direccion fisica de la pila 0 del proceso hijo
	pila_0 = (void *) (((uint_32) table_entry->base) << 12);

	task_table[new_pid].esp = loader_setup_stack(pila_0, 1, 3, NULL);

	//breakpoint();

	//la marco como activa
	task_table[new_pid].cr3 = (uint_32) h_cr3;	
	task_table[new_pid].status = TASK_ACTIVE;

	//clono FD
	device_clean(new_pid);
	device_fork(current_pid, new_pid);
	
	//finalmente agrego la tarea al scheduler
	sched_load(new_pid);
	
	//cuando el padre sale retorno el pid
	return new_pid;
}

//esta funcion carga una tarea nueva y la registra en el scheduler
pid loader_load(pso_file* f_in, int pl_in) {
	pid new_pid;
	mm_page *dir, *table_entry;
	void *ps_pila0, *ps_pila1, *ps_page;

	static int pl;
	pl = pl_in;
	static pso_file *f;
	f = f_in;

	//busco un nuevo pid para la tarea actual
	for (new_pid = 0; new_pid < MAX_PID && task_table[new_pid].status != TASK_FREE; new_pid++);
	if (new_pid == MAX_PID) {
		return MAX_PID;
	}

	task_table[new_pid].status = TASK_ACTIVE;

	//controlo que el encabezado sea correcto
	if (strncmp((char *) f->signature, "PSO", 3) != 0) {
		return MAX_PID;
	}

	if (f->mem_start < 0x400000) {
		return MAX_PID;
	}

	if (f->mem_end < f->mem_end_disk ) {
		return MAX_PID;
	}
	if ( ((uint_32) f->_main) > f->mem_end_disk || ((uint_32) f->_main) < f->mem_start) {
		return MAX_PID;
	}

	//creo el directorio de paginas para la nueva tarea
	dir = mm_dir_new();
	if (NULL == dir) {
		return MAX_PID;
	}

	//pido una pagina fisica para la pagina con la pila de anillo 0
	ps_pila0 = mm_mem_alloc();
	if (NULL == ps_pila0) {
		mm_dir_free(dir);
		return MAX_PID;
	}
	table_entry = mm_get_vt_addr_entry(dir, (void *) PILA_0);
	table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S;
	table_entry->base = ((uint_32) ps_pila0) >> 12;
 
 
 	//pido las paginas de anillo 3 
 	int i;
	for (i = 0; i < CANT_PILAS_ANILLO_3; i++) {
		ps_pila1 = mm_mem_alloc();
		if (NULL == ps_pila1) {
			mm_dir_free(dir);
			return MAX_PID;
		}
		table_entry = mm_get_vt_addr_entry(dir,(void *) (PILA_3 - (0x1000 * i)));
		table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_U;
		table_entry->base = ((uint_32) ps_pila1) >> 12;		
	 }
	 
	//pido paginas para almacenar el programa
	char *p;
	p = (char *) (f->mem_start & 0xFFFFF000); 
	while( ((uint_32) p) < f->mem_end ) {
		ps_page = mm_mem_alloc();
		if (NULL == ps_page) {
			mm_dir_free(dir);
			return MAX_PID;
		}
		table_entry = mm_get_vt_addr_entry(dir, p);
		table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_U;
		table_entry->base = ((uint_32) ps_page) >> 12;
		p += 0x1000;
	}

	static uint_32 *stack;
	stack = (uint_32*) (PILA_0 + 0x1000);

	//seteo los campos para la nueva estructura
	task_table[new_pid].cr3 = (uint_32) dir;
	task_table[new_pid].esp = (uint_32) (stack - 19);

	//intercambio los rcr3 temporalmente
	static uint_32 old_cr3;
	old_cr3 = rcr3();
	lcr3((uint_32) dir);

    //seteo toda la memoria en 0
	memset((void *) f->mem_start, 0, f->mem_end - f->mem_start );
	memcpy((void *) f->mem_start, f, f->mem_end_disk - f->mem_start );

/*
	//seteo una pila para que la tarea arranque
	*(stack - 19) = 0x0; //EBX, aparentemente el gcc también lo pushea
	*(stack - 18) = 0x0; //EBP 
	*(stack - 17) = (uint_32) task_isr_ret; //EIP 
	*(stack - 16) = 0x0; //EAX (paramentro de switchto)
	*(stack - 15) = pl | GET_DS(pl); //GS
	*(stack - 14) = pl | GET_DS(pl); //FS
	*(stack - 13) = pl | GET_DS(pl); //ES
	*(stack - 12) = pl | GET_DS(pl); //DS
	*(stack - 11) = 0x0; //EBP
	*(stack - 10) = 0x0; //EDI
	*(stack - 9) = 0x0; //ESI
	*(stack - 8) = 0x0; //EDX
	*(stack - 7) = 0x0; //ECX
	*(stack - 6) = 0x0; //EBX
	*(stack - 5) = 0x0; //EAX
	*(stack - 4) = (uint_32) f->_main; //EIP
	*(stack - 3) = pl | GET_CS(pl); //CS
	*(stack - 2) = 0x202; //EFLAGS (numero magico segun furfaro)
	*(stack - 1) = PILA_3 + 0x1000 - 4; //ESP 
	*(stack) = pl | GET_DS(pl); //SS
	// *stack es el RIP
*/
	//restauro el directorio de paginas
	lcr3(old_cr3);
	task_table[new_pid].esp = loader_setup_stack(ps_pila0, 0, pl, f->_main);
	
	device_clean(new_pid);
	
	//la marco como activa
	task_table[new_pid].status = TASK_ACTIVE;

	//finalmente agrego la tarea al scheduler
	sched_load(new_pid);

	return new_pid;
}

void loader_enqueue(int* cola) {
 
 	pid first, prev; 
	
	first = *cola;

	if (first == -1)  {
		//si la cola estaba vacia
		*cola = current_pid;
		task_table[current_pid].next = current_pid;
		task_table[current_pid].prev = current_pid;
	}
	else {
		//si no estaba vacia lo colo atras del siguiente
		prev = task_table[first].prev;
		task_table[current_pid].next = first;
		task_table[current_pid].prev = prev;
		task_table[prev].next = current_pid;
		task_table[first].prev = current_pid;
	}
	task_table[current_pid].status = TASK_SLEEP;

	loader_switchto(sched_block(current_pid)); 
}

void loader_unqueue(int* cola) {
 	pid first, next, prev; 
	
	first = *cola;

	if (first == -1) {
		return;
	}

	next = task_table[first].next;
	if (first == next) {
		//si era el unico elemento
		*cola = -1;
	}
	else {
		prev = task_table[first].prev;
		task_table[prev].next = next;
		task_table[next].prev = prev;
		*cola = next;
	}
	task_table[current_pid].status = TASK_ACTIVE;
	sched_unblock(first);
}

void loader_exit(void) {
	remove_last = 1;
	loader_switchto(sched_exit());
}

void loader_cleanup(pid old_pid){
	
	uint_32 old_cr3 = task_table[old_pid].cr3;
	memset(&task_table[old_pid], 0, sizeof(task));
	mm_dir_free((mm_page *) old_cr3);
	
	device_flush_all(old_pid);
	
	task_table[old_pid].cr3 = 0;
	task_table[old_pid].status = TASK_FINISHED;
	remove_last = 0;
}
